9  循环语句 股价涨跌幅提醒

9.1 引言循环在批量金融数据处理中的价值

金融市场产生海量的时间序列数据——股价、成交量、收益率等。循环结构使我们能够高效地处理这些重复数据,自动执行重复性任务,是构建金融分析系统的核心工具。

9.2 本章学习目标

通过本章学习,你将能够:

  1. 使用 for 循环与 range() 遍历价格与涨跌幅序列,累计计算区间收益
  2. 使用 while 循环按条件迭代,说明它与 for 的适用差异及避免死循环的要点
  3. 使用 breakcontinue 控制循环流程,并解释 for-else/while-elseelse 子句的触发条件
  4. 在循环中组合条件判断,实现股价涨跌幅预警等批量监控任务
  5. 对比 for 循环、列表推导式、zip() 与 NumPy 向量化的性能差异,选择合适的批量处理方式

先修:章节 1(第1章 常用基本数据类型)、章节 2(第2章 列表、字典和元组)、章节 3(第3章 函数定义 算术平均收益率计算)、章节 4(第4章 常用内置函数)、章节 5(第5章 NumPy 数组的创建与访问)、章节 6(第6章 NumPy 数组运算)、章节 7(第7章 NumPy 矩阵处理)

9.3 For循环基础

理论背景:迭代与递归

从计算理论来看,循环实现了迭代(Iteration)模式:

  • 迭代: 通过重复执行相同操作处理数据集合
  • 递归: 函数调用自身解决问题
  • 对比: 循环通常更高效,递归更优雅

在金融数据处理中,迭代更为常见,因为:

  • 金融数据天然具有顺序性(时间序列)
  • 内存消耗可控(不需要递归栈)
  • 更容易理解和调试

理解了”迭代”这一本质,下面进入for循环的具体语法。

9.3.1 语法结构

for循环用于遍历序列(列表、元组、字符串等)或其他可迭代对象。

语法: for item in iterable:

列表 9.1: For循环的基本语法与遍历
# =============================================================================
# 题目:For循环的基本遍历操作
# =============================================================================
# 本代码演示for循环的四种基本用法:
# 1. 遍历列表 - 最常见的场景,处理股票列表
# 2. 遍历字符串 - 处理股票代码
# 3. 遍历范围 - 生成索引或重复操作
# 4. 遍历字典 - 处理键值对数据
# for循环是金融数据处理中最常用的结构,必须熟练掌握

# ==================== 示例1:遍历列表 ====================
stocks = ['中信证券', '国泰君安', '海通证券']  # 定义券商股票列表

for stock in stocks:  # 依次取出列表中的每个股票名称
    print(f"分析股票: {stock}")  # 输出当前处理的股票
# for循环会自动遍历列表,不需要手动管理索引

# ==================== 示例2:遍历字符串 ====================
code = "600519.SH"  # 贵州茅台的股票代码
for char in code:  # 依次取出字符串中的每个字符
    print(char)  # 输出当前字符
# 可以用于解析股票代码的各个部分

# ==================== 示例3:遍历范围 ====================
for i in range(5):  # 生成0到4的整数序列
    print(f"第{i+1}次迭代")  # 输出迭代次数(从1开始计数)
# range(5)生成序列[0,1,2,3,4],不包括5

# ==================== 示例4:遍历字典 ====================
stock_info = {'name': '中信证券', 'code': '600030.SH', 'price': 24.78}  # 股票信息字典
for key in stock_info:  # 依次取出字典的键
    print(f"{key}: {stock_info[key]}")  # 输出键和对应的值
# 默认遍历字典的键,通过键访问值

9.3.2 range()函数

range()生成整数序列,是循环中最常用的工具。

语法: range(start, stop, step)

列表 9.2: range函数的多种用法
# =============================================================================
# 题目:使用range函数生成各种整数序列
# =============================================================================
# 本代码演示range函数的四种常用模式:
# 1. 指定结束值 - 最简单的用法
# 2. 指定起始和结束值 - 生成特定区间
# 3. 指定步长 - 生成等差序列
# 4. 负步长 - 倒序遍历
# range函数在金融计算中用于生成索引、时间范围等

# ==================== 用法1:指定结束值(不包含) ====================
for i in range(5):  # 生成0,1,2,3,4
    print(f"i = {i}")  # 输出: 0,1,2,3,4
# range(stop)生成0到stop-1的整数

# ==================== 用法2:指定起始和结束值 ====================
for year in range(2020, 2025):  # 生成2020到2024
    print(f"年份: {year}")  # 输出: 2020,2021,2022,2023,2024
# range(start,stop)生成start到stop-1的整数
# 常用于遍历特定年份的财务数据

# ==================== 用法3:指定步长 ====================
for i in range(0, 10, 2):  # 生成0,2,4,6,8
    print(f"偶数: {i}")  # 输出偶数序列
# range(start,stop,step)以步长step生成序列
# 常用于跳过某些数据或按固定间隔采样

# ==================== 用法4:负步长(倒序) ====================
for i in range(10, 0, -1):  # 生成10,9,8,...,1
    print(f"倒计时: {i}")  # 输出递减序列
# 负步长用于倒序遍历,如从最新到最旧的数据

# ==================== 金融应用:生成日历日 ====================
import numpy as np  # 导入NumPy库
dates = np.arange('2024-01-01', '2024-01-06', dtype='datetime64[D]')  # 生成日期序列
print("\n2024年1月前5个日历日:")  # 输出标题
for date in dates:  # 遍历日期序列
    print(f"  {date}")  # 输出每个日期
# np.arange生成日期范围,常用于时间序列分析

9.4 While循环

while循环在条件为True时重复执行,适用于不确定迭代次数的情况。

语法: while condition:

列表 9.3: While循环实现动态监控
# =============================================================================
# 题目:While循环模拟价格监控过程
# =============================================================================
# 本代码演示while循环的实际应用:
# 1. 简单计数 - 基础用法,理解循环机制
# 2. 价格监控模拟 - 模拟股票价格达到目标价的过程
# while循环适合处理不确定次数的重复任务
# 在金融交易中用于监控实时价格、等待特定条件触发

# ==================== 示例1:简单计数 ====================
count = 0  # 初始化计数器
while count < 5:  # 当计数器小于5时持续循环
    print(f"计数: {count}")  # 输出当前计数值
    count += 1  # 重要:更新循环变量,避免无限循环
# 循环执行5次后,count变为5,条件不再满足,退出循环

# ==================== 示例2:模拟价格监控 ====================
print("\n价格监控模拟:")  # 输出标题
price = 18.00  # 初始价格
target = 20.00  # 目标价格
days = 0  # 天数计数器

while price < target:  # 当价格低于目标价时持续监控
    # 模拟价格变化
    price += 0.50  # 每天价格上涨0.50元
    days += 1  # 天数加1
    print(f"第{days}天, 价格: {price:.2f}元")  # 输出每日价格

    # 安全措施:避免无限循环
    if days > 100:  # 如果监控超过100天
        print("达到最大监控天数")  # 输出警告
        break  # 强制退出循环
# break语句立即终止循环,不管条件是否满足

print(f"\n达到目标价20元, 耗时{days}天")  # 输出结果
# 实际应用中,while循环常用于实时监控市场数据

重要警告: while循环容易陷入无限循环,必须确保:

  1. 循环条件最终会变为False
  2. 循环体内有更新条件的语句
  3. 添加安全措施(如最大迭代次数)

9.5 循环控制语句

9.5.1 break跳出循环

任务要求:已知 8 个交易日的涨跌幅列表 r_list,用 for 循环逐日遍历,一旦发现当日跌幅超过 1%(i < -0.01)立即 break 跳出,并在循环结束后输出触发预警时的涨跌幅值 i。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 9.4: 平台原始代码
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
r_list=[0.0192,-0.0001,0.006,0.0074,-0.0127,-0.0067,0.0095,-0.0095]  # 定义每日涨跌幅列表
for i in r_list:  # 逐日遍历涨跌幅数据
    if i<-0.01:  # 判断当日跌幅是否超过1%
        break  # 发现跌幅超限,立即跳出循环
print('涨跌幅数据',i)  # 输出触发跌幅预警时的涨跌幅值

预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):

涨跌幅数据 -0.0127

判读要点:-0.0127 是列表中第 5 个元素(索引 4),也是第一个跌幅超过 1% 的交易日,break 使循环在该处立即终止;循环变量 i 在循环外仍可访问,保持的是触发 break 时的值,而不是列表最后一个元素 -0.0095

代码深度解析:

  1. break的执行逻辑:
    • 立即跳出最内层循环
    • 不执行循环体剩余代码
    • 继续执行循环后的语句
  2. 金融应用场景:
    • 止损触发: 价格触及止损位立即平仓
    • 异常检测: 发现异常数据停止处理
    • 目标达成: 达到盈利目标退出
  3. 变量作用域:
    • 循环变量在循环外仍然可访问
    • i保持循环中的最后一个值
    • 这是Python的特性(不是bug)

9.5.2 continue跳过当前迭代

列表 9.5: 使用continue跳过特定数据
# =============================================================================
# 题目:使用continue语句跳过特定数据
# =============================================================================
# 本代码演示continue语句的使用:
# 场景:只处理正收益的交易日,跳过负收益日
# continue语句用于过滤不需要处理的数据
# 在金融分析中常用于筛选特定条件的数据

# ==================== 定义收益率数据 ====================
returns = [0.0192, -0.0001, 0.006, 0.0074, -0.0127, 0.0095]
# 6个交易日的收益率数据

# ==================== 只处理正收益交易日 ====================
print("正收益交易日:")  # 输出标题

count = 0  # 正收益日计数器
for r in returns:  # 遍历每个收益率
    # 跳过负收益
    if r < 0:  # 如果收益率为负
        continue  # 跳过本次迭代剩余代码,直接进入下一次迭代
    # continue之后的代码不会被执行

    count += 1  # 正收益日计数加1
    print(f"  第{count}个正收益日: {r:.2%}")  # 输出正收益率
# :.2%格式化为百分比,保留2位小数

print(f"\n总计正收益日: {count}天")  # 输出正收益日总数
# 输出: 4天(跳过了2个负收益日)
# continue只跳过当前迭代,循环继续执行

9.5.3 break vs continue

关键字 作用 后续代码 循环继续
break 跳出循环 不执行,从循环后继续
continue 跳过本次 不执行,从下次迭代继续

9.6 循环中的else子句

Python的循环可以带else子句,在循环正常结束时执行(未被break中断)。

列表 9.6: 循环else子句的使用
# =============================================================================
# 题目:循环else子句的两种执行情况
# =============================================================================
# 本代码演示循环else子句的使用:
# 1. 循环正常结束 - 执行else子句
# 2. 循环被break中断 - 不执行else子句
# else子句用于区分"正常结束"和"中断退出"两种情况
# 在金融数据验证中非常有用

# ==================== 场景1:循环正常结束 ====================
prices = [10, 11, 12, 13, 14]  # 价格列表(都低于20)

for price in prices:  # 遍历价格
    if price > 20:  # 检查是否有价格超过20
        print("发现高价股票")  # 发现高价
        break  # 跳出循环
else:  # 循环未被break中断,执行else
    print("所有股票价格都在合理范围")  # 所有价格都合理
# 输出: "所有股票价格都在合理范围"

print("\n" + "="*50 + "\n")  # 输出分隔线

# ==================== 场景2:循环被break中断 ====================
prices2 = [10, 11, 25, 13, 14]  # 价格列表(包含25)

for price in prices2:  # 遍历价格
    if price > 20:  # 检查是否有价格超过20
        print("发现高价股票")  # 发现高价(25)
        break  # 跳出循环
else:  # 由于break被执行,else不会执行
    print("所有股票价格都在合理范围")
# 输出: "发现高价股票" (else子句未执行)

金融应用: 验证所有数据是否符合条件

列表 9.7: 使用循环else验证数据完整性
# =============================================================================
# 题目:使用循环else验证数据完整性
# =============================================================================
# 本代码演示如何使用循环else进行数据验证:
# 场景:验证所有价格是否为正数
# 如果发现任何异常价格(<=0),立即中断并提示
# 如果所有价格都正常,输出验证通过的提示

# ==================== 定义价格数据 ====================
prices = [24.78, 20.15, 14.03, 22.41, 16.17]  # 股票价格列表
# 所有价格都是正数

# ==================== 验证所有价格是否为正数 ====================
for price in prices:  # 遍历每个价格
    if price <= 0:  # 检查是否有非正价格
        print(f"发现异常价格: {price}")  # 输出异常价格
        break  # 发现异常,立即中断
else:  # 循环正常结束(未发现异常)
    print("✓ 所有价格数据有效")  # 输出验证通过信息
# 输出: "✓ 所有价格数据有效"
# 这种模式在金融数据清洗中非常常用

9.7 实战案例股价涨跌幅监控

列表 9.8: 综合循环实现股价监控系统
# =============================================================================
# 题目:股价涨跌幅实时监控系统
# =============================================================================
# 本代码实现一个完整的股价监控系统:
# 1. 模拟一周的股价数据
# 2. 逐日计算涨跌幅
# 3. 根据涨跌幅判断市场状态
# 4. 触发预警时自动停止监控
# 这个系统综合运用了for循环、if条件判断、break语句等核心概念
# 是实际量化交易系统的基础框架

# ==================== 导入NumPy库 ====================
import numpy as np  # 导入用于生成随机数和数值计算

# ==================== 模拟一周的股价数据 ====================
np.random.seed(42)  # 设置随机种子,确保结果可重现
base_price = 20.0  # 起始价格
price_changes = np.random.randn(5) * 0.5  # 生成5日价格变化(标准差0.5)
prices = base_price + np.cumsum(price_changes)  # 累加变化得到价格序列
# np.cumsum计算累积和,模拟价格的随机游走

# ==================== 输出系统标题 ====================
print("=" * 60)  # 输出分隔线
print("股价涨跌幅监控系统")  # 输出系统名称
print("=" * 60)  # 输出分隔线

# ==================== 设定预警阈值 ====================
alert_threshold = -0.01  # 跌幅超过-1%触发警报

# ==================== 逐日监控股价 ====================
for i, price in enumerate(prices, 1):  # 遍历价格序列,i从1开始
    # 计算涨跌幅
    if i == 1:  # 第一天没有前一日数据
        daily_return = 0  # 涨跌幅设为0
    else:  # 其他天数
        daily_return = (price - prices[i-2]) / prices[i-2]  # 计算收益率
    # prices[i-2]是前一日价格(因为enumerate从1开始,i-2才是正确的索引)

    # 转换为百分比
    return_pct = daily_return * 100  # 转换为百分比形式

    # 判断市场状态
    if daily_return < alert_threshold:  # 跌幅超过阈值
        status = "⚠️ 警告"  # 风险警告状态
        action = "考虑减仓"  # 建议减仓
    elif daily_return > 0.01:  # 涨幅超过1%
        status = "✓ 上涨"  # 上涨状态
        action = "继续持有"  # 建议持有
    else:  # 正常波动范围
        status = "正常"  # 正常状态
        action = "观望"  # 建议观望

    # 输出每日监控报告
    print(f"\n{i}天:")  # 输出天数
    print(f"  收盘价: {price:.2f}元")  # 输出收盘价
    print(f"  涨跌幅: {return_pct:+.2f}%")  # 输出涨跌幅(+表示正号)
    print(f"  状态: {status}")  # 输出状态
    print(f"  建议: {action}")  # 输出操作建议

    # 检查是否需要停止监控
    if daily_return < alert_threshold:  # 如果触发预警
        print("\n" + "=" * 60)  # 输出分隔线
        print("触发风险预警,系统暂停监控")  # 输出预警信息
        print("=" * 60)  # 输出分隔线
        break  # 立即停止监控
else:  # 循环正常结束(未触发break)
    # 循环正常结束(未触发警报)
    print("\n" + "=" * 60)  # 输出分隔线
    print("一周监控结束,市场表现平稳")  # 输出总结信息
    print("=" * 60)  # 输出分隔线
# for-else结构:正常结束时执行else,break中断时不执行

9.8 嵌套循环

在处理二维数据(如多只股票多日收益)时,需要使用嵌套循环。

列表 9.9: 嵌套循环处理多股票多日数据
# =============================================================================
# 题目:嵌套循环统计每日上涨股票数量
# =============================================================================
# 本代码演示嵌套循环的使用:
# 场景:3只股票,5个交易日的收益率数据
# 任务:统计每个交易日有多少只股票上涨
# 嵌套循环用于处理二维数据结构(外层遍历天数,内层遍历股票)
# 这是金融数据分析中的常见模式

# ==================== 定义收益率数据 ====================
returns = [
    [0.01, 0.02, -0.01, 0.03, 0.02],  # 股票A的5日收益率
    [0.02, -0.01, 0.03, 0.01, -0.02],  # 股票B的5日收益率
    [-0.01, 0.03, 0.02, -0.02, 0.01]   # 股票C的5日收益率
]
# 这是一个3行5列的二维列表(矩阵)

stock_names = ['股票A', '股票B', '股票C']  # 股票名称列表

# ==================== 统计每日正收益股票 ====================
print("每日正收益股票统计:\n")  # 输出标题

for day in range(5):  # 外层循环:遍历5个交易日
    positive_count = 0  # 正收益股票计数器
    positive_stocks = []  # 正收益股票名称列表

    for stock_idx in range(3):  # 内层循环:遍历3只股票
        ret = returns[stock_idx][day]  # 获取第stock_idx只股票在第day天的收益率
        # returns[stock_idx]是第stock_idx只股票的所有收益率
        # [day]取出该股票在第day天的收益率

        if ret > 0:  # 如果收益率为正
            positive_count += 1  # 计数器加1
            positive_stocks.append(stock_names[stock_idx])  # 添加股票名称

    # 输出当日统计结果
    print(f"第{day+1}天: {positive_count}只股票上涨")  # 输出上涨股票数量
    print(f"  上涨股票: {', '.join(positive_stocks)}")  # 输出上涨股票名称
    # ', '.join()将列表元素用逗号连接成字符串

性能提示: 嵌套循环时间复杂度为O(n×m),数据量大时应考虑使用NumPy向量化操作。

9.9 循环性能优化

列表 9.10: 循环与向量化的性能对比
# =============================================================================
# 题目:不同循环方法的性能对比测试
# =============================================================================
# 本代码演示四种数组相加方法的性能差异:
# 1. Python for循环 - 最慢,但最易懂
# 2. 列表推导 - 较快,更Pythonic
# 3. zip+列表推导 - 更Pythonic,代码更清晰
# 4. NumPy向量化 - 最快,推荐用于大数据
# 性能差异在大数据集上非常明显,NumPy可快数百倍
# 在量化交易中,性能优化直接影响策略回测速度

# ==================== 导入时间库 ====================
import time  # 用于计时

# ==================== 生成大数据集 ====================
n = 100000  # 数据规模:10万个元素
list1 = list(range(n))  # 生成列表[0,1,2,...,99999]
list2 = list(range(n))  # 生成相同的列表

# ==================== 方法1:Python for循环(慢) ====================
start = time.time()  # 记录开始时间
result_loop = []  # 存储结果
for i in range(n):  # 遍历所有元素
    result_loop.append(list1[i] + list2[i])  # 相加并添加到结果列表
loop_time = time.time() - start  # 计算耗时
# for循环最慢,因为每次迭代都有Python解释器开销

# ==================== 方法2:列表推导(较快) ====================
start = time.time()  # 记录开始时间
result_listcomp = [list1[i] + list2[i] for i in range(n)]  # 列表推导
listcomp_time = time.time() - start  # 计算耗时
# 列表推导比for循环快,因为内部优化

# ==================== 方法3:zip+列表推导(更Pythonic) ====================
start = time.time()  # 记录开始时间
result_zip = [a + b for a, b in zip(list1, list2)]  # 使用zip配对
zip_time = time.time() - start  # 计算耗时
# zip函数将两个列表的元素配对,代码更清晰

# ==================== 方法4:NumPy向量化(最快) ====================
import numpy as np  # 导入NumPy
arr1 = np.array(list1)  # 转换为NumPy数组
arr2 = np.array(list2)  # 转换为NumPy数组
start = time.time()  # 记录开始时间
result_numpy = arr1 + arr2  # 直接相加(向量化操作)
numpy_time = time.time() - start  # 计算耗时
# NumPy使用C语言实现向量化操作,速度极快

# ==================== 输出性能对比结果 ====================
print(f"Python循环: {loop_time:.4f}秒")  # 输出循环耗时
print(f"列表推导: {listcomp_time:.4f}秒")  # 输出列表推导耗时
print(f"zip方法: {zip_time:.4f}秒")  # 输出zip方法耗时
print(f"NumPy向量化: {numpy_time:.4f}秒")  # 输出NumPy耗时
print(f"\n最快方法比最慢快 {loop_time/numpy_time:.1f}倍")  # 输出性能倍数
# 在大数据集上,NumPy通常比纯Python循环快100-1000倍
# 这就是为什么量化交易必须使用NumPy/Pandas的原因

9.10 本章小结

要点:

  • for 适合遍历已知序列,while 适合条件驱动的迭代;range(start, stop, step) 含头不含尾
  • break 立即跳出最内层循环,continue 只结束本次迭代
  • for-else/while-elseelse 子句只在循环未被 break 打断时执行
  • 循环变量在循环结束后仍可访问,保持最后一次使用的值
  • 批量任务优先选择列表推导式或 NumPy 向量化,大规模数据下性能差距可达百倍以上

易错点:

  • while 的条件更新语句写错,会造成死循环或循环体一次都不执行
  • 嵌套循环中 break 只跳出最内层,误以为它会终止全部循环
  • 循环体从未执行时就使用循环变量,会触发 NameError
  • 在遍历列表的同时增删其元素,导致元素被跳过或重复处理

9.11 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    r_list = [0.0192, -0.0001, 0.006, 0.0074, -0.0127, -0.0067, 0.0095, -0.0095]
    for i in r_list:
        if i < -0.01:
            break
    else:
        print("无跌幅超限日")
    print(i)
    total = 0
    k = 0
    while k < len(r_list):
        total += r_list[k]
        k += 1
    print(round(total, 4))

    参考答案(先写下你的预测再点开)

    解题思路:逐段推演。第一段 for 循环从左到右遍历:0.0192-0.00010.0060.0074 都不满足 i < -0.01;轮到第 5 个元素 -0.0127 时条件成立,执行 break 立即跳出。因为循环是被 break 中断而非正常跑完,for-elseelse 子句不执行,屏幕上不会出现“无跌幅超限日”。循环结束后 i 仍可访问,保持触发 break 时的值,输出 -0.0127(而不是列表末尾的 -0.0095)。第二段 while 循环从 k = 0 累加到 k = 7,把 8 个涨跌幅全部累进 total,和为 0.0131(浮点累加有微小误差,round(total, 4) 后恰为 0.0131),输出 0.0131

    # 验证脚本:for-else的break行为与while累加求和
    r_list = [0.0192, -0.0001, 0.006, 0.0074, -0.0127, -0.0067, 0.0095, -0.0095]  # 8日涨跌幅列表
    for i in r_list:  # 逐日遍历
        if i < -0.01:  # 跌幅超过1%
            break  # 立即跳出,else不执行
    else:  # 仅当循环未被break打断才执行
        print('无跌幅超限日')
    print(i)  # i保持触发break时的值
    total = 0  # 累加器清零
    k = 0  # 下标从0开始
    while k < len(r_list):  # 未遍历完则继续
        total += r_list[k]  # 累加当日涨跌幅
        k += 1  # 下标前移(漏写会死循环)
    print(round(total, 4))  # 保留4位小数输出总和

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    -0.0127
    0.0131

    回扣本章:对应本章小结“要点”第 3、4 条——else 子句只在循环未被 break 打断时执行,循环变量在循环结束后保持最后一次使用的值。

  2. 概念辨析:breakcontinue 对循环流程的影响有何不同?上面第 1 题中 else 子句为什么没有输出,怎样的数据才会让它执行?再说明:forwhile 各自适用的场景,以及”循环结束后 i 的值”由什么决定。

    参考答案(点开前请先独立完成)

    解题思路:逐点作答。第一,break 立即终止整个循环(跳出最内层循环体,执行循环之后的语句),而 continue 只结束本次迭代,跳过本轮剩余语句、直接进入下一轮,循环本身继续。第二,第 1 题中 else 没有输出是因为循环在第 5 个元素处被 break 中断——for-elseelse 只在序列被完整遍历、没有触发 break 时执行;只有当 r_list 中所有元素都满足 i >= -0.01(如 [0.01, -0.005, 0.008],本机实测)时,循环正常跑完,else 才会输出提示。第三,for 适用于遍历已知的有限序列(列表、range、字典等),迭代次数由序列长度决定;while 适用于迭代次数事先未知、由条件驱动的场景(如“价格未达目标价就继续监控”),但要保证循环体内有推动条件变化的语句,否则死循环。第四,循环结束后 i 的值由“最后一次赋给它的元素”决定:正常结束就是序列最后一个元素;被 break 中断就是触发 break 那一轮的元素(第 1 题即 -0.0127);若循环体一次都没执行(如空列表),i 从未被赋值,循环外使用会抛 NameError

    回扣本章:对应本章小结“要点”第 1、2、3、4 条与“易错点”第 1、3 条——break/continue 的差别、else 触发条件、for/while 适用场景与循环变量的残留值。

  3. 变式任务(平台任务同型改造):沿用平台任务的 r_list,把 break 改成 continue,统计跌幅超过 1% 的交易日天数与它们的涨跌幅值;再用 while 循环改写平台任务,输出第一个跌幅超过 1% 的交易日是第几个交易日(从 1 开始计数);最后用 for-else 改写,当全月没有跌幅超过 1% 的交易日时输出”本月无跌幅预警”。

    参考答案(点开前请先独立完成)

    解题思路:三段各自改写。第一段把 break 换成 continue:对不满足“跌幅超过 1%”的日子执行 continue 跳过,满足的累计入列表,循环跑完全程——r_list 中只有 -0.0127 一个超限值(-0.0067-0.0095 均大于 -0.01),故天数为 1。第二段用 while 加下标从 0 扫描,找到第一个满足 r_list[day] < -0.01 的下标即输出“第几个交易日”(下标加 1 转为从 1 计数),-0.0127 在下标 4,即第 5 个交易日。第三段 for-else:把预警输出放在循环体、break 中断,把“无预警”提示放在 else,全月无超限日时循环正常结束、else 输出“本月无跌幅预警”;演示时另用一组无超限日的数据 r_mild 触发该分支(r_list 本身有超限日,走的是 break 分支)。

    # 变式程序:continue统计、while定位首个超限日与for-else无预警判断
    r_list = [0.0192, -0.0001, 0.006, 0.0074, -0.0127, -0.0067, 0.0095, -0.0095]  # 沿用平台任务数据
    drop_days = []  # 存放跌幅超过1%的涨跌幅值
    for i in r_list:  # 逐日遍历
        if i >= -0.01:  # 未超限的日子
            continue  # 跳过本轮,继续下一日
        drop_days.append(i)  # 超限日记录其涨跌幅
    print('跌幅超限天数', len(drop_days))  # 输出超限天数
    print('对应涨跌幅', drop_days)  # 输出超限日的涨跌幅值
    day_index = 0  # 交易日下标(从0开始)
    while day_index < len(r_list):  # 未扫描完则继续
        if r_list[day_index] < -0.01:  # 找到第一个跌幅超限日
            print('第一个跌幅超限日是第', day_index + 1, '个交易日')  # 下标加1转为从1计数
            break  # 找到即退出
        day_index += 1  # 未找到,检查下一日
    r_mild = [0.01, -0.005, 0.008, -0.009]  # 全月无跌幅超过1%的示例数据
    for i in r_mild:  # 遍历全月数据
        if i < -0.01:  # 检查是否跌幅超限
            print('发现跌幅预警', i)  # 输出预警值并break
            break
    else:  # 循环未被break打断,说明全月无超限
        print('本月无跌幅预警')

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    跌幅超限天数 1
    对应涨跌幅 [-0.0127]
    第一个跌幅超限日是第 5 个交易日
    本月无跌幅预警

    注意:以上为本题变式的独立代码;列表 9.4 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应本章小结“要点”第 1、2、3 条——continue 只结束本次迭代、while 适合条件驱动的扫描、for-elseelse 只在未触发 break 时执行。

  4. 动手验证:分别用 for 循环、列表推导式与 NumPy 向量化三种方式,把 r_list 中小于 0 的涨跌幅替换为 0 并输出结果,确认三者结果一致;再用 time 模块对 100 万个随机数做同样处理,比较三种方式的耗时。

    参考答案(点开前请先独立完成)

    解题思路:正确性部分,三种写法逐元素执行同一规则“负值置 0、非负保留”:for 循环逐个判断后 append;列表推导式 [0 if i < 0 else i for i in r_list] 一行完成;NumPy 用 np.where(r_array < 0, 0, r_array) 对整个布尔条件数组一次性选择。三者数值完全一致(列表里 0 显示为 0,NumPy 数组里显示为 0.,显示形式不同、值相同)。性能部分对 100 万个标准正态随机数重复同样处理:纯 Python for 循环要执行 100 万次解释器层面的判断与 append,列表推导省去重复的 append 调用略快,NumPy 向量化把整个循环下沉到 C 层一次完成,通常快一个数量级以上;具体耗时随机器与当时负载波动,应以量级对比为准。

    # 验证脚本(一):三种写法的正确性对比
    import numpy as np  # 导入NumPy库
    r_list = [0.0192, -0.0001, 0.006, 0.0074, -0.0127, -0.0067, 0.0095, -0.0095]  # 8日涨跌幅
    result_loop = []  # 存放for循环结果
    for i in r_list:  # 逐日遍历
        if i < 0:  # 负涨跌幅
            result_loop.append(0)  # 替换为0
        else:
            result_loop.append(i)  # 非负保留
    print(result_loop)  # 输出for循环结果
    result_comp = [0 if i < 0 else i for i in r_list]  # 列表推导式一行完成
    print(result_comp)  # 输出列表推导结果
    r_array = np.array(r_list)  # 转为NumPy数组
    print(np.where(r_array < 0, 0, r_array))  # 向量化条件替换

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    [0.0192, 0, 0.006, 0.0074, 0, 0, 0.0095, 0]
    [0.0192, 0, 0.006, 0.0074, 0, 0, 0.0095, 0]
    [0.0192 0.     0.006  0.0074 0.     0.     0.0095 0.    ]
    # 验证脚本(二):100万随机数上三种方式的耗时对比
    import numpy as np  # 导入NumPy库
    import time  # 导入时间模块用于计时
    np.random.seed(42)  # 固定随机种子保证可复现
    big_returns = np.random.randn(1000000)  # 生成100万个随机数
    start = time.time()  # 记录for循环开始时间
    loop_result = []  # 存放循环结果
    for v in big_returns:  # 逐元素循环
        loop_result.append(0 if v < 0 else v)  # 负值置0
    loop_cost = time.time() - start  # for循环耗时
    start = time.time()  # 记录列表推导开始时间
    comp_result = [0 if v < 0 else v for v in big_returns]  # 列表推导批量处理
    comp_cost = time.time() - start  # 列表推导耗时
    start = time.time()  # 记录向量化开始时间
    numpy_result = np.where(big_returns < 0, 0, big_returns)  # NumPy向量化处理
    numpy_cost = time.time() - start  # 向量化耗时
    print('for循环耗时 {:.4f} 秒'.format(loop_cost))  # 输出各项耗时
    print('列表推导耗时 {:.4f} 秒'.format(comp_cost))
    print('NumPy向量化耗时 {:.4f} 秒'.format(numpy_cost))
    print('NumPy 比循环快 {:.1f} 倍'.format(loop_cost / numpy_cost))  # 加速比
    print('三法结果一致', np.allclose(loop_result, comp_result) and np.allclose(loop_result, numpy_result))  # 校验一致

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准;耗时随机器与负载波动,量级关系稳定):

    for循环耗时 0.1226 秒
    列表推导耗时 0.0868 秒
    NumPy向量化耗时 0.0078 秒
    NumPy 比循环快 15.8 倍
    三法结果一致 True

    回扣本章:对应本章小结“要点”第 5 条——批量任务优先选择列表推导式或 NumPy 向量化,大规模数据下性能差距可达十倍到百倍以上。